<?php
// ----------
// form.php
// ----------

namespace Forms;
use Forms;

class Form implements Forms\Constants
{
  // public variables
  public $renderHTML          = array();// variable with render HTML
  
  // method for constants
  const METHOD_POST           = 'post';
  const METHOD_GET            = 'get';
  const METHOD_FILE           = 'file';
  
  const WARNING_MESSAGE_1     = 'Field %s not allowed function %s() in field type %s';
  const WARNING_MESSAGE_2     = 'Function %s must have first parametr an array';
  const WARNING_MESSAGE_3     = 'Field %s have two or more same values';

  const SECURE_CODE_LENGTH    = 40;// length of secure code
  
  // private variables
  private $formData           = null; // variable for form data
  private $secureCode         = null;// variable for secure code
  private $errorMessages      = array();// variable with form errors, after form send
  private $setting            = array();// form settings <form '. implode( ' ' , $settings ) .''>
  private $variablesList      = array();// form variables list

  // generate basic
  public function __construct( $action = null , $method = self::METHOD_POST , $formID = null )
  {
    if( !in_Array( strToLower( $method ) , array( self::METHOD_POST , self::METHOD_GET , self::METHOD_FILE ) ) )
    {
      trigger_error( 'method must be POST, GET or FILE' , E_USER_WARNING );
    }

    // add secure code
    if( in_Array( strToLower( $method ) , array( self::METHOD_POST , self::METHOD_FILE ) ) )
    {
      preg_match( '/^(.*?\.php\?)(.*?)$/' , $action , $pregResult );// edit for future
      $this->secureCode   = $this->generateSecureCode();// generate secure code
      $getSecureCode      = ( $action == null ? '?'. $this->secureCode : ( $pregResult == null ? '?'. $this->secureCode : '&amp;'. $this->secureCode ) );// create access token to the seanding header
      $action             = ( $action == null ? baseName( $_SERVER['PHP_SELF'] ) . $getSecureCode : $action . $getSecureCode );
      $this->renderHTML[] = sprintf( '<input type=\'hidden\' name=\'%s\' value=\'1\' />' , $this->secureCode );// add secure code to the form
    }
    else
    {
      $action             = ( $action == null ? baseName( $_SERVER['PHP_SELF'] ) : $action );
    }
  
    if( strToLower( $method ) != self::METHOD_FILE )
    {
      $this->controll['method'] = strToLower( $method );
    }
    else
    {
      $this->controll['method']   = self::METHOD_POST;
      $this->settings['enctype']  = self::SET_ENCTYPE;// add enctype to file upload
    }

    $this->settings['method'] = sprintf( self::SET_METHOD , $this->controll['method'] );
    $this->settings['action'] = sprintf( self::SET_ACTION , $action );

    if( !empty( $formID ) ) { $this->settings['formStyle']  = sprintf( self::SET_ID , $formID ); }
  }
  
  // check if form already send
  private function checkIfFormAlreadySend()
  {
    switch( $this->controll['method'] )
    {
      // check if POST/FILE form already send
      case self::METHOD_POST:
      case self::METHOD_FILE:
        if( strToLower( $_SERVER['REQUEST_METHOD'] ) == $this->controll['method'] )
        {
          // CSRF protect
          //if( ( count( $this->variablesList ) + 1 ) == ( count( $_POST ) + count( $_FILES ) ) )
          //{
            $arrayDifferent = array_diff_key( $_POST , $this->variablesList ) ;
            if( count( $arrayDifferent ) == 1 )
            {
              forEach( $arrayDifferent as $key => $value )
                 return ( isSet( $_GET[$key] ) ? true : false );
            }
            else
            {
              return false;
            }
          //}
        }
        else
        {
          return false;
        }
       break;
       
      // check if GET form already send
      case self::METHOD_GET:
        return ( strToLower( $_SERVER['REQUEST_METHOD'] ) == $this->controll['method'] ? true : false );
       break;
       
      // error prevent
      default: return false; break;
    }
  }
  
  // function for generate secure code
  private function generateSecureCode( $length = self::SECURE_CODE_LENGTH )
  {
    $code = null;
    $codeArray = array( 'a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' , 'k' , 'l' , 'm' , 'n' , 'o' , 'p' , 'q' , 'r' , 's' , 't' , 'u' , 'v' , 'w' , 'x' , 'y' , 'z' , 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 );
    for( $i = 0; $i < $length; $i++ ) { $code .= $codeArray[rand( 0 , count( $codeArray ) - 1 )]; }
    return md5( $code );
  }

  // render form
  public function renderForm()
  {
    //global $config;
    if( $this->checkIfFormAlreadySend() && !empty( $this->errorMessages ) )
    {
      $this->renderHTML[] = "\n" . '<ul style=\'padding-left: 2em; color: #bc2a4d\'>';
      forEach( $this->errorMessages as $key )
      {
        //$this->renderHTML[] = sprintf( '<li style=\'list-style-image: url( "%s" );\'>%s</li>' , $config->template_image_dir .'/vykricnik.png' , $key );
        $this->renderHTML[] = sprintf( '<li>%s</li>' , $key );
      }
      $this->renderHTML[] = '</ul>';
      $this->renderHTML[] = '<br />';
    }

    // php error fix, with user add zero fields
    if( $this->formData != null )
    {
      $this->renderHTML[] = '<dl>';
      forEach( $this->formData as $value )
      {
        $this->renderHTML[]                     = $value->renderField();
        $this->variablesList[$value->fieldName] = true;
      }
      $this->renderHTML[] = '</dl>';
    }
    return sprintf( "\n<form %s>\n%s\n</form>\n" , implode( ' ' , $this->settings ) , implode( "\n" , $this->renderHTML ) );
  }

  // form validate
  public function isSubmited()
  {
    if( $this->checkIfFormAlreadySend() )
    {
      forEach( $this->formData as $key => $value )
      {
        switch( $value->fieldType )
        {
          case inputCheckbox::TYPE_CHECKBOX:
            $data     = $this->requestData( $key );
            $required = $value->getRequired();

            // must be checked and data must be equal to set value
            if( $required['required'] && $data == false )
            {
              $this->errorMessages[$key] = $required['message'];
            }
           break;

          case inputRadio::TYPE_RADIO:
            $data     = $this->requestData( $key );
            $required = $value->getRequired();

            // must be marked radio
            if( $required['required'] && !isSet( $data ) && in_Array( $data , $value->radioValueArray ) )
            {
              $this->errorMessages[$key] = $required['message'];
            }
           break;

          case selectList::TYPE_SELECT:
            $data     = $this->requestData( $key );
            $required = $value->getRequired();

            // must be selected in list
            if( $required['required'] && !isSet( $data ) && in_Array( $data , $value->optionValueArray ) )
            {
              $this->errorMessages[$key] = $required['message'];
            }
           break;
           
          case inputField::TYPE_FILE:
            $data     = $this->requestData( $key );
            $required = $value->getRequired();
            $rules    = $value->getRules();

            if( !empty( $rules ) )
            {
              forEach( $rules as $rule )
              {
                if( function_exists( $rule['rule'] ) )
                {
                  if( !$rule['rule']( $data ) )
                  {
                    $this->errorMessages[$key] = $rule['message'];
                    break;
                  }
                }
              }
            }
            elseif( $required['required'] && $data['error'] == '4' )
            {
              $this->errorMessages[$key] = $required['message'];
            }
           break;
           
          default:
            $data     = $this->requestData( $key );
            $required = $value->getRequired();
            $compared = $value->getCompared();
            $rules    = $value->getRules();

            // without rules, but must any input
            if( $required['required'] && empty( $rules ) && ( $data == null || $data == '' ) )
            {
              $this->errorMessages[$key] = $required['message'];
            }
            // must by set
            elseIf( $required['required'] && ( $data == null || $data == '' ) )
            {
              $this->errorMessages[$key] = $required['message'];
            }
            // check with compare field
            elseIf( $compared && $data != $this->requestData( $compared['compared'] ) )
            {
              $this->errorMessages[$key] = $compared['message'];
            }
            // set rule, but field can be empty
            elseIf( !$required['required'] && !empty( $rules ) && ( $data != null || $data != '' ) )
            {
              forEach( $rules as $rule )
              {
                if( function_exists( $rule['rule'] ) )
                {
                  if( !$rule['rule']( $data ) )
                  {
                    $this->errorMessages[$key] = $rule['message'];
                    break;
                  }
                }
                else
                {
                  $getRule = ( empty( $rule['settings'] ) ? $rule['rule'] : vsprintf( $rule['rule'] , $rule['settings'] ) );
                  if( !preg_match( $getRule , $data ) )
                  {
                    $this->errorMessages[$key] = $rule['message'];
                    break;
                  }
                }
              }
            }
            // set rule and field must be set
            elseIf( $required['required'] && !empty( $rules ) )
            {
              forEach( $rules as $rule )
              {
                if( function_exists( $rule['rule'] ) )
                {
                  if( !$rule['rule']( $data ) )
                  {
                    $this->errorMessages[$key] = $rule['message'];
                    break;
                  }
                }
                else
                {
                  $getRule = ( empty( $rule['settings'] ) ? $rule['rule'] : vsprintf( $rule['rule'] , $rule['settings'] ) );
                  if( !preg_match( $getRule , $data ) )
                  {
                    $this->errorMessages[$key] = $rule['message'];
                    break;
                  }
                }
              }
            }
           break;
        }
      }
      return ( count( $this->errorMessages ) == 0 ? true : false );
    }
    else
    {
      return false;
    }
  }

  // request data from form
  public function requestData( $data )
  {
    return ( $this->controll['method'] == self::METHOD_POST ? ( isSet( $_POST[$data] ) ? $_POST[$data] : null ) : ( $this->controll['method'] == self::METHOD_GET ? ( isSet( $_GET[$data] ) ? $_GET[$data] : null ) : null ) );
  }

  // input text
  public function addInputText( $name , $value = null )
  {
    $value                        = ( $this->checkIfFormAlreadySend() ? $this->requestData( $name ) : $value );
    $this->variablesList[$name]   = $value;
    return $this->formData[$name] = new Forms\inputField( $name , ( strToLower( $_SERVER['REQUEST_METHOD'] ) == $this->controll['method'] ? $this->requestData( $name ) : $value ) , inputField::TYPE_TEXT );
  }

  // input password
  public function addInputPassword( $name , $value = null )
  {
    $value                        = ( $this->checkIfFormAlreadySend() ? $this->requestData( $name ) : $value );
    $this->variablesList[$name]   = $value;
    return $this->formData[$name] = new Forms\inputField( $name , $value , inputField::TYPE_PASSWORD );
  }

  // input hidden
  public function addInputHidden( $name , $value = null )
  {
    $value                        = ( $this->checkIfFormAlreadySend() ? $this->requestData( $name ) : $value );
    $this->variablesList[$name]   = $value;
    return $this->formData[$name] = new Forms\inputField( $name , $value , inputField::TYPE_HIDDEN );
  }
  
  // input file
  public function addInputFile( $name )
  {
    $value                        = ( $this->checkIfFormAlreadySend() ? $this->requestData( $name ) : null );
    $this->variablesList[$name]   = $value;
    return $this->formData[$name] = new Forms\inputField( $name , null , inputField::TYPE_FILE );
  }

  // input submit
  public function addInputSubmit( $name , $value = null )
  {
    $value                        = ( $this->checkIfFormAlreadySend() ? $this->requestData( $name ) : $value );
    $this->variablesList[$name]   = $value;
    return $this->formData[$name] = new Forms\inputField( $name , $value , inputField::TYPE_SUBMIT );
  }

  // input reset
  public function addInputReset( $name , $value = null )
  {
    $value                        = ( $this->checkIfFormAlreadySend() ? $this->requestData( $name ) : $value );
    $this->variablesList[$name]   = $value;
    return $this->formData[$name] = new Forms\inputField( $name , $value , inputField::TYPE_RESET );
  }

  // input checkbox
  public function addInputCheckbox( $name , $checked = false )
  {
    $checked                      = ( $this->checkIfFormAlreadySend() ? $this->requestData( $name ) : $checked );
    $this->variablesList[$name]   = $checked;
    return $this->formData[$name] = new Forms\inputCheckbox( $name , $checked );
  }

  // textarea
  public function addTextArea( $name , $value = false )
  {
    $value                        = ( $this->checkIfFormAlreadySend() ? $this->requestData( $name ) : $value );
    $this->variablesList[$name]   = $value;
    return $this->formData[$name] = new Forms\textArea( $name , $value );
  }

  // radio
  public function addRadioList( $name )
  {
    $check                        = ( $this->checkIfFormAlreadySend() ? $this->requestData( $name ) : null );
    $this->variablesList[$name]   = $check;
    return $this->formData[$name] = new Forms\inputRadio( $name , $check );
  }

  // select
  public function addSelectList( $name )
  {
    $select                       = ( $this->checkIfFormAlreadySend() ? $this->requestData( $name ) : null );
    $this->variablesList[$name]   = $select;
    return $this->formData[$name] = new Forms\selectList( $name , $select );
  }
}

// class with define rules
class Rules
{
  const MIN_LENGTH  = '/^[\w\d]{%s,}$/';// min characters
  const MAX_LENGTH  = '/^[\w\d]{0,%s}$/';// max characters
  const LENGTH      = '/^[\w\d]{%s,%s}$/';// min and max characters
  const EQUAL       = '/^[\w\d]{%s}$/';// equal characters

  const DIGIT       = '/^([0-9]+)$/';// numbers
  const DECIMALS    = '/^-?(0|[1-9][0-9]*|(?=\.))(\.[0-9]+)?$/';// negative/positive numbers/decimals numbers
  const WORD        = '/^([a-zA-Z0-9_]+)$/';//	písmena, číslice a podtržítko
  const EMAIL       = '/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/';// email
  const PATTERN     = '%s';// user regex
}

interface Constants
{
  // settings
  const SET_ACTION            = 'action=\'%s\'';
  const SET_METHOD            = 'method=\'%s\'';
  const SET_ENCTYPE           = 'enctype=\'multipart/form-data\'';
  const SET_CLASS             = 'class=\'%s\'';
  const SET_ID                = 'id=\'%s\'';
  const SET_STYLE             = 'style=\'%s\'';
  const SET_SIZE              = 'size=\'%d\'';
  const SET_MAXLENGTH         = 'maxlength=\'%d\'';
  const SET_LIST              = 'list=\'%s\'';
  const SET_PLACEHOLDER       = 'placeholder=\'%s\'';
  const SET_ONCHANGE          = 'onchange=\'%s\'';
  const SET_DISABLED          = 'disabled=\'disabled\'';
  const SET_AUTOCOMPLETE_ON   = 'autocomplete=\'on\'',
        SET_AUTOCOMPLETE_OFF  = 'autocomplete=\'off\'';
  const SET_TYPE              = 'type=\'%s\'';
  const SET_NAME              = 'name=\'%s\'';
  const SET_VALUE             = 'value=\'%s\'';
  const SET_CHECKED           = 'checked=\'checked\'';
  const SET_COLS              = 'cols=\'%s\'';
  const SET_ROWS              = 'rows=\'%s\'';
  const SET_MULTIPLE          = 'multiple';
  // style
  const SET_DT_STYLE          = 'float: left; clear: left';
  const SET_DD_STYLE          = 'margin: 0 0 0 20em; padding: 0 0 0.5em 0';
  
  // id
  const SET_DT_ID             = 'id-for-dt-';
  const SET_DD_ID             = 'id-for-dd-';
}

// basic class for all forms
class Base implements Forms\Constants
{
  protected $rules      = array();
  protected $required   = false;
  protected $compared   = false;
  protected $formLabel  = null;
  protected $radioList  = array();
  protected $htmlAfter  = array();
  protected $htmlBefore = array();

  // create label
  public function setLabel( $label , $others = null )
  {
    if( !in_Array( $this->fieldType , array( inputField::TYPE_HIDDEN ) ) )
    {
      if( is_Array( $others ) )
      {
        $data = array();
        // set style, if set for label
        forEach( $others as $key => $value )
        {
          switch( $key )
          {
            case 'class': $data['class']  = sprintf( self::SET_CLASS , $value );
             break;
            case 'id':    $data['id']     = sprintf( self::SET_ID , $value );
             break;
            case 'style': $data['style']  = sprintf( self::SET_STYLE , $value );
             break;
          }
        }
      }
      $description = ( !is_Array( $others ) && !empty( $others ) ? sprintf( '<br /><div style=\'width: 20em\'>%s</div>' , $others ) : null );

      // different labels for different forms
      switch( $this->fieldType )
      {
        case inputCheckbox::TYPE_CHECKBOX: $this->formLabel = ( empty( $data ) ? sprintf( '<label for=\'%s\'>%s</label>' , '%s' , $label ) : sprintf( '<label for=\'%s\' %s>%s</label>' , '%s' , implode( ' ' , $data ) , $label ) );// label without double dot
         break;

        default: $this->formLabel = ( empty( $data ) ? sprintf( '<label for=\'%s\'>%s:%s</label>' , '%s' , $label , $description ) : sprintf( '<label for=\'%s\' %s>%s:%s</label>' , '%s' , implode( ' ' , $data ) , $label , $description ) );// default label with doble dot
         break;
      }
    }
    else
    {
      trigger_error( sprintf( Forms\Form::WARNING_MESSAGE_1 , $this->fieldName , __FUNCTION__ , $this->fieldType ) , E_USER_WARNING );
    }
    return $this;
  }

  // add class
  public function setClass( $class )
  {
    if( !in_Array( $this->fieldType , array( inputField::TYPE_HIDDEN ) ) )
    {
      $this->settings['class'] = sprintf( self::SET_CLASS , $class );
    }
    else
    {
      trigger_error( sprintf( Forms\Form::WARNING_MESSAGE_1 , $this->fieldName , __FUNCTION__ , $this->fieldType ) , E_USER_WARNING );
    }
    return $this;
  }

  // add ID
  public function setID( $id )
  {
    if( !in_Array( $this->fieldType , array( inputField::TYPE_HIDDEN ) ) )
    {
      $this->settings['id'] = sprintf( self::SET_ID , $id );
      $this->settings['id_name'] = $id;// save ID for label (only if user set)
    }
    else
    {
      trigger_error( sprintf( Forms\Form::WARNING_MESSAGE_1 , $this->fieldName , __FUNCTION__ , $this->fieldType ) , E_USER_WARNING );
    }
    return $this;
  }

  // add style
  public function setStyle( $style )
  {
    if( !in_Array( $this->fieldType , array( inputField::TYPE_HIDDEN ) ) )
    {
      $this->settings['style'] = sprintf( self::SET_STYLE , $style );
    }
    else
    {
      trigger_error( sprintf( Forms\Form::WARNING_MESSAGE_1 , $this->fieldName , __FUNCTION__ , $this->fieldType ) , E_USER_WARNING );
    }
    return $this;
  }

  // set size
  public function setSize( $size )
  {
    if( in_Array( $this->fieldType , array( inputField::TYPE_TEXT , inputField::TYPE_PASSWORD , selectList::TYPE_SELECT ) ) )
    {
      $this->settings['size'] = sprintf( self::SET_SIZE , $size );
    }
    else
    {
      trigger_error( sprintf( Forms\Form::WARNING_MESSAGE_1 , $this->fieldName , __FUNCTION__ , $this->fieldType ) , E_USER_WARNING );
    }
    return $this;
  }

  // add max input length
  public function setMaxlength( $maxlength )
  {
    if( in_Array( $this->fieldType , array( inputField::TYPE_TEXT , inputField::TYPE_PASSWORD , textArea::TYPE_TEXTAREA ) ) )
    {
      $this->settings['maxlength'] = sprintf( self::SET_MAXLENGTH , $maxlength );
    }
    else
    {
      trigger_error( sprintf( Forms\Form::WARNING_MESSAGE_1 , $this->fieldName , __FUNCTION__ , $this->fieldType ) , E_USER_WARNING );
    }
    return $this;
  }

  // add help list
  public function setList( $list )
  {
    if( in_Array( $this->fieldType , array( inputField::TYPE_TEXT ) ) )
    {
      $this->settings['list'] = sprintf( self::SET_LIST , $list );
    }
    else
    {
      trigger_error( sprintf( Forms\Form::WARNING_MESSAGE_1 , $this->fieldName , __FUNCTION__ , $this->fieldType ) , E_USER_WARNING );
    }
    return $this;
  }

  // add grey text on background
  public function setPlaceholder( $placeholder )
  {
    if( in_Array( $this->fieldType , array( inputField::TYPE_TEXT , inputField::TYPE_PASSWORD ) ) )
    {
      $this->settings['placeholder'] = sprintf( self::SET_PLACEHOLDER , $placeholder );
    }
    else
    {
      trigger_error( sprintf( Forms\Form::WARNING_MESSAGE_1 , $this->fieldName , __FUNCTION__ , $this->fieldType ) , E_USER_WARNING );
    }
    return $this;
  }

  // add autocomplete
  public function setAutocomplete( $autocomplete )
  {
    if( in_Array( $this->fieldType , array( inputField::TYPE_TEXT ) ) )
    {
      $this->settings['autocompleted'] = ( $autocompleted ? self::SET_AUTOCOMPLETE_ON : self::SET_AUTOCOMPLETE_OFF );
    }
    else
    {
      trigger_error( sprintf( Forms\Form::WARNING_MESSAGE_1 , $this->fieldName , __FUNCTION__ , $this->fieldType ) , E_USER_WARNING );
    }
    return $this;
  }

  // add cols
  public function setCols( $cols )
  {
    if( in_Array( $this->fieldType , array( textArea::TYPE_TEXTAREA ) ) )
    {
      $this->settings['cols'] = sprintf( self::SET_COLS , $cols );
    }
    else
    {
      trigger_error( sprintf( Forms\Form::WARNING_MESSAGE_1 , $this->fieldName , __FUNCTION__ , $this->fieldType ) , E_USER_WARNING );
    }
    return $this;
  }

  // set rows
  public function setRows( $rows )
  {
    if( in_Array( $this->fieldType , array( textArea::TYPE_TEXTAREA ) ) )
    {
      $this->settings['rows'] = sprintf( self::SET_ROWS , $rows );
    }
    else
    {
      trigger_error( sprintf( Forms\Form::WARNING_MESSAGE_1 , $this->fieldName , __FUNCTION__ , $this->fieldType ) , E_USER_WARNING );
    }
    return $this;
  }

  // add checked
  public function setChecked()
  {
    if( in_Array( $this->fieldType , array( inputCheckbox::TYPE_CHECKBOX ) ) )
    {
      $this->settings['checked'] = self::SET_CHECKED;
    }
    else
    {
      trigger_error( sprintf( Forms\Form::WARNING_MESSAGE_1 , $this->fieldName , __FUNCTION__ , $this->fieldType ) , E_USER_WARNING );
    }
    return $this;
  }

  // set on change field
  public function setOnChange( $script )
  {
    if( in_Array( $this->fieldType , array( selectList::TYPE_SELECT ) ) )
    {
      $this->settings['onchange'] = sprintf( self::SET_ONCHANGE , $script );
    }
    else
    {
      trigger_error( sprintf( Forms\Form::WARNING_MESSAGE_1 , $this->fieldName , __FUNCTION__ , $this->fieldType ) , E_USER_WARNING );
    }
    return $this;
  }

  // disabled field
  public function setDisabled( $disabled )
  {
    $this->settings['disabled'] = ( $disabled ? self::SET_DISABLED : null );
    return $this;
  }

  // field rule
  public function setRule( $rule , $message = 'Function <strong>setRule()</strong> must have set the error message!' , $settings = null )
  {
    if( in_Array( $this->fieldType , array( inputField::TYPE_TEXT , inputField::TYPE_PASSWORD , inputField::TYPE_FILE , textArea::TYPE_TEXTAREA ) ) )
    {
      $this->rules[] = array( 'rule' => $rule, 'settings' => $settings , 'message' =>  $message );
    }
    else
    {
      trigger_error( sprintf( Forms\Form::WARNING_MESSAGE_1 , $this->fieldName , __FUNCTION__ , $this->fieldType ) , E_USER_WARNING );
    }
    return $this;
  }

  // set required field
  public function setRequired( $message = 'Function <strong>setRequired()</strong> must have set the error message!' )
  {
    if( in_Array( $this->fieldType , array( inputField::TYPE_TEXT , inputField::TYPE_PASSWORD , inputField::TYPE_FILE , inputCheckbox::TYPE_CHECKBOX , textArea::TYPE_TEXTAREA , inputRadio::TYPE_RADIO , selectList::TYPE_SELECT ) ) )
    {
      $this->required['required'] = true;
      $this->required['message']  = $message;
    }
    else
    {
      trigger_error( sprintf( Forms\Form::WARNING_MESSAGE_1 , $this->fieldName , __FUNCTION__ , $this->fieldType ) , E_USER_WARNING );
    }
    return $this;
  }

  // compared field
  public function setCompared( $comparedField , $message = 'Function <strong>setCompared()</strong> must have set the error message!' )
  {
    if( in_Array( $this->fieldType , array( inputField::TYPE_TEXT , inputField::TYPE_PASSWORD , textArea::TYPE_TEXTAREA ) ) )
    {
      $this->compared['compared'] = $comparedField;
      $this->compared['message']  = $message;
    }
    else
    {
      trigger_error( sprintf( Forms\Form::WARNING_MESSAGE_1 , $this->fieldName , __FUNCTION__ , $this->fieldType ) , E_USER_WARNING );
    }
    return $this;
  }
  // set radio data
  public function setRadio( $label , $value , $check = false )
  {
    if( in_Array( $this->fieldType , array( inputRadio::TYPE_RADIO ) ) )
    {
      $array['label'] = $label;
      $array['value'] = $value;
      $array['check'] = $check;
      $this->radioList[] = $array;
    }
    else
    {
      trigger_error( sprintf( Forms\Form::WARNING_MESSAGE_1 , $this->fieldName , __FUNCTION__ , $this->fieldType ) , E_USER_WARNING );
    }
    return $this;
  }

  // set option data
  public function setOption( $label , $value , $selected = false )
  {
    if( in_Array( $this->fieldType , array( selectList::TYPE_SELECT ) ) )
    {
      $array['label']     = $label;
      $array['value']     = $value;
      $array['selected']  = $selected;
      $this->optionList[] = $array;
    }
    else
    {
      trigger_error( sprintf( Forms\Form::WARNING_MESSAGE_1 , $this->fieldName , __FUNCTION__ , $this->fieldType ) , E_USER_WARNING );
    }
    return $this;
  }

  // set option data in array
  public function setOptionArray( $array , $selected = null )
  {
    if( in_Array( $this->fieldType , array( selectList::TYPE_SELECT ) ) )
    {
      if( is_Array( $array ) )
      {
        forEach( $array as $value => $label )
        {
          $finalArray['label']     = $label;
          $finalArray['value']     = $value;
          $finalArray['selected']  = ( $selected == $value ? true : false );
          $this->optionList[]      = $finalArray;
        }
      }
      else
      {
        trigger_error( sprintf( Forms\Form::WARNING_MESSAGE_2 , __FUNCTION__ ) , E_USER_WARNING );
      }
    }
    else
    {
      trigger_error( sprintf( Forms\Form::WARNING_MESSAGE_1 , $this->fieldName , __FUNCTION__ , $this->fieldType ) , E_USER_WARNING );
    }
    return $this;
  }
  
  // set multiple choice to select list
  public function setMultiple( $multiple = false )
  {
    if( in_Array( $this->fieldType , array( selectList::TYPE_SELECT ) ) )
    {
      $this->settings['multiple'] = self::SET_MULTIPLE;
    }
    else
    {
      trigger_error( sprintf( Forms\Form::WARNING_MESSAGE_1 , $this->fieldName , __FUNCTION__ , $this->fieldType ) , E_USER_WARNING );
    }
    return $this;
  }

  // add html to the form
  public function addHTML( $type , $htmlDT , $htmlDD = null )
  {
    if( empty( $htmlDD ) )
    {
      switch( $type )
      {
        case 'before': $this->htmlBefore[] = sprintf( '<dt>%s</dt>' , $htmlDT ); break;
        case 'after':  $this->htmlAfter[]  = sprintf( '<dt>%s</dt>' , $htmlDT ); break;
      }
    }
    else
    {
      switch( $type )
      {
        case 'before': $this->htmlBefore[] = sprintf( '<dt style=\'float: left; clear: left\'>%s</dt><dd style=\'margin: 0 0 0 20em; padding: 0 0 0.5em 0\'>%s</dd>' , $htmlDT , $htmlDD ); break;
        case 'after':  $this->htmlAfter[]  = sprintf( '<dt style=\'float: left; clear: left\'>%s</dt><dd style=\'margin: 0 0 0 20em; padding: 0 0 0.5em 0\'>%s</dd>' , $htmlDT , $htmlDD ); break;
      }
    }
    return $this;
  }
}

// class for create input text, password, submit button, reset button and hidden input
class inputField extends Forms\Base implements Forms\Constants
{
  const TYPE_TEXT       = 'text',
        TYPE_PASSWORD   = 'password',
        TYPE_SUBMIT     = 'submit',
        TYPE_RESET      = 'reset',
        TYPE_HIDDEN     = 'hidden',
        TYPE_FILE       = 'file';

  public    $errorArray   = array();// public variable for errors in this field
  public    $fieldType    = null;// private function
  public    $fieldName    = null;// public variable for field name
  protected $settings     = array();// protected variable for field settings
  private   $renderData   = null;// private variable for render data

  // basic
  public function __construct( $name , $value , $type )
  {
    $this->fieldType = $type;
    $this->fieldName = $name;

    switch( $this->fieldType )
    {
      case 'text':      $this->settings['type'] = sprintf( self::SET_TYPE , self::TYPE_TEXT );      break;
      case 'password':  $this->settings['type'] = sprintf( self::SET_TYPE , self::TYPE_PASSWORD );  break;
      case 'submit':    $this->settings['type'] = sprintf( self::SET_TYPE , self::TYPE_SUBMIT );    break;
      case 'reset':     $this->settings['type'] = sprintf( self::SET_TYPE , self::TYPE_RESET );     break;
      case 'hidden':    $this->settings['type'] = sprintf( self::SET_TYPE , self::TYPE_HIDDEN );    break;
      case 'file':      $this->settings['type'] = sprintf( self::SET_TYPE , self::TYPE_FILE );      break;
    }
    $this->settings['name']  = sprintf( self::SET_NAME , $this->fieldName );
    $this->settings['value'] = sprintf( self::SET_VALUE , $value );
    
    // ID and style for DD and DT tags
    $this->dtSettings['style'] = sprintf( self::SET_STYLE , self::SET_DT_STYLE );
    $this->dtSettings['id']    = sprintf( self::SET_ID , self::SET_DT_ID . $this->fieldName );
    $this->ddSettings['style'] = sprintf( self::SET_STYLE , self::SET_DD_STYLE );
    $this->ddSettings['id']    = sprintf( self::SET_ID , self::SET_DD_ID . $this->fieldName );
  }

  // get rules
  public function getRules()
  {
    return $this->rules;
  }

  // get required
  public function getRequired()
  {
    return $this->required;
  }

  // get compared
  public function getCompared()
  {
    return $this->compared;
  }

  // render field
  public function renderField()
  {
    if( empty( $this->formLabel ) )
    {
       $this->renderData = sprintf( '%s<dt %s><input %s /></dt>%s' , implode( '' , $this->htmlBefore ) , $this->dtSettings['id'] , implode( ' ' , $this->settings ) , implode( '' , $this->htmlAfter ) );
    }
    else
    {
      if( !empty( $this->settings['id'] ) )
      {
        $this->formLabel = sprintf( $this->formLabel , $this->settings['id_name'] );
        unSet( $this->settings['id_name'] );// if user set ID, must be unset before implode settings
      }
      else
      {
        $this->formLabel = sprintf( $this->formLabel , sprintf( 'id-for-%s' , $this->fieldName ) );
        $this->settings['id'] = sprintf( self::SET_ID , sprintf( 'id-for-%s' , $this->fieldName ) );
      }
      $this->renderData = sprintf( '%s<dt %s>%s</dt><dd %s><input %s /></dd>%s' , implode( '' , $this->htmlBefore ) , implode( ' ' , $this->dtSettings ) , $this->formLabel , implode( ' ' , $this->ddSettings ) , implode( ' ' , $this->settings ) , implode( '' , $this->htmlAfter ) );
    }
    return $this->renderData;
  }
}

// class for checkbox
class inputCheckbox extends Forms\Base implements Forms\Constants
{
  const TYPE_CHECKBOX   = 'checkbox';

  public    $errorArray   = array();// public variable for errors in this field
  public    $fieldType    = null;// private function
  public    $fieldName    = null;// public variable for field name
  protected $settings     = array();// protected variable for field settings
  private   $renderData   = null;// private variable for render data
  
  // basic
  public function __construct( $name , $checked = false )
  {
    $this->fieldName = $name;
    $this->fieldType = self::TYPE_CHECKBOX;
    
    $this->settings['type'] = sprintf( self::SET_TYPE , self::TYPE_CHECKBOX );
    $this->settings['name'] = sprintf( self::SET_NAME , $this->fieldName );
    if( $checked ) { $this->settings['checked'] = self::SET_CHECKED; }
    
    // ID for DD and DT tags
    $this->dtSettings['id'] = sprintf( self::SET_ID , self::SET_DT_ID . $this->fieldName );
    $this->ddSettings['id'] = sprintf( self::SET_ID , self::SET_DD_ID . $this->fieldName );
  }

  // get required
  public function getRequired()
  {
    return $this->required;
  }

  // render field
  public function renderField()
  {
    if( empty( $this->formLabel ) )
    {
       $this->renderData = sprintf( '%s<dt %s><input %s /></dt>%s' , implode( '' , $this->htmlBefore ) , $this->ddSettings['id'] , implode( ' ' , $this->settings ) , implode( '' , $this->htmlAfter ) );
    }
    else
    {
      if( !empty( $this->settings['id'] ) )
      {
        $this->formLabel = sprintf( $this->formLabel , $this->settings['id_name'] );
        unSet( $this->settings['id_name'] );
      }
      else
      {
        $this->formLabel = sprintf( $this->formLabel , sprintf( 'id-for-%s' , $this->fieldName ) );
        $this->settings['id'] = sprintf( self::SET_ID , sprintf( 'id-for-%s' , $this->fieldName ) );
      }
      $this->renderData =  sprintf( '%s<dt %s><input %s />&nbsp;%s</dt>%s' , implode( '' , $this->htmlBefore ) , $this->ddSettings['id'] , implode( ' ' , $this->settings ) , $this->formLabel , implode( '' , $this->htmlAfter ) );
    }
    return $this->renderData;
  }
}

// class for text area
class textArea extends Forms\Base implements Forms\Constants
{
  const TYPE_TEXTAREA   = 'textarea';
  
  public    $errorArray   = array();// public variable for errors in this field
  public    $fieldType    = null;// private function
  public    $fieldName    = null;// public variable for field name
  protected $settings     = array();// protected variable for field settings
  private   $renderData   = null;// private variable for render data

  // basic
  public function __construct( $name , $value )
  {
    $this->fieldName     = $name;
    $this->fieldType     = self::TYPE_TEXTAREA;
    $this->textAreaValue = $value;
    
    $this->settings['name'] = sprintf( self::SET_NAME , $this->fieldName );
    
    // ID and style for DD and DT tags
    $this->dtSettings['style'] = sprintf( self::SET_STYLE , self::SET_DT_STYLE );
    $this->dtSettings['id']    = sprintf( self::SET_ID , self::SET_DT_ID . $this->fieldName );
    $this->ddSettings['style'] = sprintf( self::SET_STYLE , self::SET_DD_STYLE );
    $this->ddSettings['id']    = sprintf( self::SET_ID , self::SET_DD_ID . $this->fieldName );
  }

  // get rules
  public function getRules()
  {
    return $this->rules;
  }

  // get required
  public function getRequired()
  {
    return $this->required;
  }

  // get compared
  public function getCompared()
  {
    return $this->compared;
  }

  // render field
  public function renderField()
  {
    if( empty( $this->formLabel ) )
    {
       $this->renderData = sprintf( '%s<dt %s><textarea %s>%s</textarea></dt>%s' , implode( '' , $this->htmlBefore ) , $this->dtSettings['id'] , implode( ' ' , $this->settings ) , $this->textAreaValue , implode( '' , $this->htmlAfter ) );
    }
    else
    {
      if( !empty( $this->settings['id'] ) )
      {
        $this->formLabel = sprintf( $this->formLabel , $this->settings['id_name'] );
        unSet( $this->settings['id_name'] );// if user set ID, must be unset before implode settings
      }
      else
      {
        $this->formLabel      = sprintf( $this->formLabel , sprintf( 'id-for-%s' , $this->fieldName ) );
        $this->settings['id'] = sprintf( self::SET_ID , sprintf( 'id-for-%s' , $this->fieldName ) );
      }
      $this->renderData =  sprintf( '%s<dt %s>%s</dt><dd %s><textarea %s>%s</textarea></dd>%s' , implode( '' , $this->htmlBefore ) , implode( ' ' , $this->dtSettings ) , $this->formLabel , implode( ' ' , $this->ddSettings ) , implode( ' ' , $this->settings ) , $this->textAreaValue , implode( '' , $this->htmlAfter ) );
    }
    return $this->renderData;
  }
}

// class for radio
class inputRadio extends Forms\Base implements Forms\Constants
{
  const TYPE_RADIO   = 'radio';
  
  public    $errorArray      = array();// public variable for errors in this field
  public    $fieldType       = null;// private function
  public    $fieldName       = null;// public variable for field name
  public    $radioValueArray = array();// public array variable for radio values
  protected $settings        = array();// protected variable for field settings
  protected $checkedValue    = null;// protected variable for checked value in radio
  private   $renderData      = null;// private variable for render data

  // basic
  public function __construct( $name , $checkedValue = null )
  {
    $this->fieldName        = $name;
    $this->fieldType        = self::TYPE_RADIO;
    $this->checkedValue     = $checkedValue;
    
    $this->settings['type'] = sprintf( self::SET_TYPE , self::TYPE_RADIO );
    $this->settings['name'] = sprintf( self::SET_NAME , $this->fieldName );
    
    // ID and style for DD and DT tags
    $this->dtSettings['style'] = sprintf( self::SET_STYLE , self::SET_DT_STYLE );
    $this->dtSettings['id']    = sprintf( self::SET_ID , self::SET_DT_ID . $this->fieldName );
    $this->ddSettings['style'] = sprintf( self::SET_STYLE , self::SET_DD_STYLE );
    $this->ddSettings['id']    = sprintf( self::SET_ID , self::SET_DD_ID . $this->fieldName );
  }

  // get required
  public function getRequired()
  {
    return $this->required;
  }

  // render field
  public function renderField()
  {
    $radio       = array();
    $labelID     = null;
    $firsRadioID = null;

    // generate radio to the array
    forEach( $this->radioList as $key => $value )
    {
      // fail, if two same value
      if( !in_Array( $value['value'] , $this->radioValueArray ) )
      {
        $this->settings['value'] = sprintf( self::SET_VALUE , $value['value'] );
        $id = sprintf( 'id-for-radio-%s-%s' , $this->fieldName , $value['value'] );
        $this->setID( $id );
        unSet( $this->settings['id_name'] );
        $this->radioValueArray[] = $value['value'];

        // selected radio after form send
        if( $this->checkedValue == $value['value'] && empty( $labelID ) )
        {
          $this->settings['checked'] = self::SET_CHECKED;
          $radio[]                   = sprintf( '<input %s />&nbsp;<label for=\'%s\'>%s</label>' , implode( ' ' , $this->settings ) , $id , $value['label'] );
          unSet( $this->settings['checked'] );
          $labelID = $id;
        }
        // selected radio by user
        elseIf( $value['check'] && empty( $labelID ) )
        {
          $this->settings['checked'] = self::SET_CHECKED;
          $radio[]                   = sprintf( '<input %s />&nbsp;<label for=\'%s\'>%s</label>' , implode( ' ' , $this->settings ) , $id , $value['label'] );
          unSet( $this->settings['checked'] );
          $labelID = $id;
        }
        // no selected radio
        else
        {
          $radio[] = sprintf( '<input %s />&nbsp;<label for=\'%s\'>%s</label>' , implode( ' ' , $this->settings ) , $id , $value['label'] );
        }
        $firsRadioID = ( empty( $firsRadioID ) ? $id : $firsRadioID );// get first ID in radio list for future use
      }
      else
      {
        trigger_error( sprintf( Forms\Form::WARNING_MESSAGE_3 , $this->fieldName ) , E_USER_WARNING );
      }
    }

    // final HTML render
    if( empty( $this->formLabel ) )
    {
       $this->renderData = sprintf( '%s<dt %s>%s</dt>%s' , implode( '' , $this->htmlBefore ) , $this->dtSettings['id'] , implode( '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' , $radio ) , implode( '' , $this->htmlAfter ) );
    }
    else
    {
      $labelID         = ( empty( $labelID ) ? $firsRadioID : $labelID );
      $this->formLabel = sprintf( $this->formLabel , $labelID );
      $this->renderData = sprintf( '%s<dt %s>%s</dt><dd %s>%s</dd>%s' , implode( '' , $this->htmlBefore ) , implode( ' ' , $this->dtSettings ) , $this->formLabel , implode( ' ' , $this->ddSettings ) , implode( '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' , $radio ) , implode( '' , $this->htmlAfter ) );
    }
    return $this->renderData;
  }
}

// class for select list
class selectList extends Forms\Base implements Forms\Constants
{
  const TYPE_SELECT           = 'select';
  
  public    $errorArray       = array();// public variable for errors in this field
  public    $fieldType        = null;// private function
  public    $fieldName        = null;// public variable for field name
  public    $optionValueArray = array();// public array variable for values in list
  protected $settings         = array();// protected variable for field settings
  protected $selectedValue    = null;// protected variable for selected value
  private   $renderData       = null;// private variable for render data

  // basic
  public function __construct( $name , $selectedValue = null )
  {
    $this->fieldName        = $name;
    $this->fieldType        = self::TYPE_SELECT;
    $this->selectedValue    = $selectedValue;
    
    $this->settings['name'] = sprintf( self::SET_NAME , $this->fieldName );
    
    // ID and style for DD and DT tags
    $this->dtSettings['style'] = sprintf( self::SET_STYLE , self::SET_DT_STYLE );
    $this->dtSettings['id']    = sprintf( self::SET_ID , self::SET_DT_ID . $this->fieldName );
    $this->ddSettings['style'] = sprintf( self::SET_STYLE , self::SET_DD_STYLE );
    $this->ddSettings['id']    = sprintf( self::SET_ID , self::SET_DD_ID . $this->fieldName );
  }

  // get required
  public function getRequired()
  {
    return $this->required;
  }

  // render field
  public function renderField()
  {
    $option   = array();
    $selected = false;

    // generate list to the array
    if( !empty( $this->optionList ) )
    {
      forEach( $this->optionList as $key => $value )
      {
        // fail, if two same value
        if( !in_Array( $value['value'] , $this->optionValueArray ) )
        {
          $this->optionValueArray[] = $value['value'];

          if( $this->selectedValue != null && $this->selectedValue == $value['value'] && !$selected )
          {
            $selected = true;
            $option[] = sprintf( '<option value=\'%s\' selected=\'selected\'>%s</option>' , $value['value'] , $value['label'] );
          }
          elseIf( $value['selected'] && !$selected )
          {
            $selected = true;
            $option[] = sprintf( '<option value=\'%s\' selected=\'selected\'>%s</option>' , $value['value'] , $value['label'] );
          }
          else
          {
            $option[] = sprintf( '<option value=\'%s\'>%s</option>' , $value['value'] , $value['label'] );
          }
        }
        else
        {
          trigger_error( sprintf( Forms\Form::WARNING_MESSAGE_3 , $this->fieldName ) , E_USER_WARNING );
        }
      }
    }

    // final HTML render
    if( empty( $this->formLabel ) )
    {
       $this->renderData = sprintf( '%s<dt %s><select %s>%s</select></dt>%s' , implode( '' , $this->htmlBefore ) , $this->dtSettings['id'] , implode( ' ' , $this->settings ) , implode( "\n" , $option ) , implode( '' , $this->htmlAfter ) );
    }
    else
    {
      $this->formLabel = sprintf( $this->formLabel , sprintf( 'id-for-%s' , $this->fieldName ) );
      $this->settings['id'] = sprintf( self::SET_ID , sprintf( 'id-for-%s' , $this->fieldName ) );
      $this->renderData =  sprintf( '%s<dt %s>%s</dt><dd %s><select %s>%s</select></dd>%s' , implode( '' , $this->htmlBefore ) , implode( ' ' , $this->dtSettings ) , $this->formLabel , implode( ' ' , $this->ddSettings ) , implode( ' ' , $this->settings ) , implode( "\n" , $option ) , implode( '' , $this->htmlAfter ) );
    }
    return $this->renderData;
  }
}

?>